﻿using System;
using System.Collections;
using System.ComponentModel;
using System.Diagnostics;
using System.Globalization;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;

namespace AvalonEdit.Sample
{
    public class AutoFilteredComboBox : ComboBox
    {
        //#endregion

        private int _silenceEvents;

        /// <summary>
        /// Creates a new instance of <see cref="AutoFilteredComboBox" />.
        /// </summary>
        public AutoFilteredComboBox()
        {
            DependencyPropertyDescriptor textProperty = DependencyPropertyDescriptor.FromProperty(
                TextProperty, typeof (AutoFilteredComboBox));
            textProperty.AddValueChanged(this, OnTextChanged);

            RegisterIsCaseSensitiveChangeNotification();
        }

        #region IsCaseSensitive Dependency Property

        /// <summary>
        /// The <see cref="DependencyProperty"/> object of the <see cref="IsCaseSensitive" /> dependency property.
        /// </summary>
        public static readonly DependencyProperty IsCaseSensitiveProperty =
            DependencyProperty.Register("IsCaseSensitive", typeof (bool), typeof (AutoFilteredComboBox),
                                        new UIPropertyMetadata(false));

        /// <summary>
        /// Gets or sets the way the combo box treats the case sensitivity of typed text.
        /// </summary>
        /// <value>The way the combo box treats the case sensitivity of typed text.</value>
        [Description("The way the combo box treats the case sensitivity of typed text.")]
        [Category("AutoFiltered ComboBox")]
        [DefaultValue(true)]
        public bool IsCaseSensitive
        {
            [DebuggerStepThrough]
            get { return (bool) GetValue(IsCaseSensitiveProperty); }
            [DebuggerStepThrough]
            set { SetValue(IsCaseSensitiveProperty, value); }
        }

        protected virtual void OnIsCaseSensitiveChanged(object sender, EventArgs e)
        {
            if (IsCaseSensitive)
                IsTextSearchEnabled = false;

            RefreshFilter();
        }

        private void RegisterIsCaseSensitiveChangeNotification()
        {
            DependencyPropertyDescriptor.FromProperty(IsCaseSensitiveProperty, typeof (AutoFilteredComboBox)).
                AddValueChanged(
                    this, OnIsCaseSensitiveChanged);
            //System.ComponentModel.DependencyPropertyDescriptor.FromProperty(ItemTemplateProperty, typeof(AutoFilteredComboBox)).AddValueChanged(
            //   this, this.OnItemTemplateChanged);
        }

        #endregion

        #region DropDownOnFocus Dependency Property

        /// <summary>
        /// The <see cref="DependencyProperty"/> object of the <see cref="DropDownOnFocus" /> dependency property.
        /// </summary>
        public static readonly DependencyProperty DropDownOnFocusProperty =
            DependencyProperty.Register("DropDownOnFocus", typeof (bool), typeof (AutoFilteredComboBox),
                                        new UIPropertyMetadata(true));

        /// <summary>
        /// Gets or sets the way the combo box behaves when it receives focus.
        /// </summary>
        /// <value>The way the combo box behaves when it receives focus.</value>
        [Description("The way the combo box behaves when it receives focus.")]
        [Category("AutoFiltered ComboBox")]
        [DefaultValue(true)]
        public bool DropDownOnFocus
        {
            [DebuggerStepThrough]
            get { return (bool) GetValue(DropDownOnFocusProperty); }
            [DebuggerStepThrough]
            set { SetValue(DropDownOnFocusProperty, value); }
        }

        #endregion

        #region | Handle selection |

        private int _length;
        private int _start;

        /// <summary>
        /// Gets the text box in charge of the editable portion of the combo box.
        /// </summary>
        protected TextBox EditableTextBox
        {
            get { return ((TextBox) GetTemplateChild("PART_EditableTextBox")); }
        }

        /// <summary>
        /// Called when <see cref="ComboBox.ApplyTemplate()"/> is called.
        /// </summary>
        public override void OnApplyTemplate()
        {
            base.OnApplyTemplate();

            EditableTextBox.SelectionChanged += EditableTextBoxSelectionChanged;
        }

        private void EditableTextBoxSelectionChanged(object sender, RoutedEventArgs e)
        {
            if (_silenceEvents == 0)
            {
                _start = ((TextBox) (e.OriginalSource)).SelectionStart;
                _length = ((TextBox) (e.OriginalSource)).SelectionLength;

                RefreshFilter();
            }
        }

        #endregion

        #region | Handle focus |

        /// <summary>
        /// Invoked whenever an unhandled <see cref="UIElement.GotFocus" /> event
        /// reaches this element in its route.
        /// </summary>
        /// <param name="e">The <see cref="RoutedEventArgs" /> that contains the event data.</param>
        protected override void OnGotFocus(RoutedEventArgs e)
        {
            base.OnGotFocus(e);

            if (ItemsSource != null && DropDownOnFocus)
            {
                IsDropDownOpen = true;
            }
        }

        #endregion

        #region | Handle filtering |

        private void RefreshFilter()
        {
            if (ItemsSource != null)
            {
                ICollectionView view = CollectionViewSource.GetDefaultView(ItemsSource);
                view.Refresh();
                IsDropDownOpen = true;
            }
        }

        private bool FilterPredicate(object value)
        {
            // We don't like nulls.
            if (value == null)
                return false;

            // If there is no text, there's no reason to filter.
            if (Text.Length == 0)
                return true;

            string prefix = Text;

            // If the end of the text is selected, do not mind it.
            if (_length > 0 && _start + _length == Text.Length)
            {
                prefix = prefix.Substring(0, _start);
            }

            return value.ToString()
                .StartsWith(prefix, !IsCaseSensitive, CultureInfo.CurrentCulture);
        }

        #endregion

        /// <summary>
        /// Called when the source of an item in a selector changes.
        /// </summary>
        /// <param name="oldValue">Old value of the source.</param>
        /// <param name="newValue">New value of the source.</param>
        protected override void OnItemsSourceChanged(IEnumerable oldValue, IEnumerable newValue)
        {
            if (newValue != null)
            {
                ICollectionView view = CollectionViewSource.GetDefaultView(newValue);
                view.Filter += FilterPredicate;
            }

            if (oldValue != null)
            {
                ICollectionView view = CollectionViewSource.GetDefaultView(oldValue);
                view.Filter -= FilterPredicate;
            }

            base.OnItemsSourceChanged(oldValue, newValue);
        }

        private void OnTextChanged(object sender, EventArgs e)
        {
            if (!IsTextSearchEnabled && _silenceEvents == 0)
            {
                RefreshFilter();

                // Manually simulate the automatic selection that would have been
                // available if the IsTextSearchEnabled dependency property was set.
                if (Text.Length > 0)
                {
                    foreach (object item in CollectionViewSource.GetDefaultView(ItemsSource))
                    {
                        int text = item.ToString().Length, prefix = Text.Length;
                        SelectedItem = item;

                        _silenceEvents++;
                        EditableTextBox.Text = item.ToString();
                        EditableTextBox.Select(prefix, text - prefix);
                        _silenceEvents--;
                        break;
                    }
                }
            }
        }
    }
}